package cn.starlightsoftware.sherly.framework.security.config;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTUtil;
import cn.starlightsoftware.sherly.db.cache.LoginUserDetailsCache;
import cn.starlightsoftware.sherly.framework.security.entity.LoginUserDetails;
import cn.starlightsoftware.sherly.framework.tenant.config.TenantConfig;
import cn.starlightsoftware.sherly.framework.tenant.config.TenantContextHolder;
import cn.starlightsoftware.sherly.framework.utils.StreamUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableAutoConfiguration(exclude = {UserDetailsServiceAutoConfiguration.class})
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private TenantConfig tenantConfig;

    private final AntPathMatcher pathMatcher = new AntPathMatcher();

    @Resource
    private AccessDeniedHandler accessDeniedHandler;

    @Resource
    private AuthenticationEntryPoint authenticationEntryPoint;

    @Value("${sherly.auth.jwt-secret}")
    private String jwtSecret;

    @Resource
    private LoginUserDetailsCache loginUserDetailsRedisDAO;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
                //关闭csrf
                .csrf().disable()

                // 配置token认证处理器
                .addFilterBefore(createOncePerRequestFilter(), UsernamePasswordAuthenticationFilter.class)

                // 配置认证失败处理器
                .exceptionHandling().accessDeniedHandler(accessDeniedHandler).authenticationEntryPoint(authenticationEntryPoint)

                //不通过Session获取SecurityContext
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)

                .and()
                .authorizeRequests()
                // 静态资源
                .antMatchers(
                        HttpMethod.GET,
                        "/*.html",
                        "/**/*.html",
                        "/**/*.css",
                        "/**/*.js",
                        "/webSocket/**"
                ).permitAll()
                // knife4j文档
                .antMatchers("/swagger-ui.html").permitAll()
                .antMatchers("/swagger-resources/**").permitAll()
                .antMatchers("/webjars/**").permitAll()
                .antMatchers("/*/api-docs/**").permitAll()
                // app相关接口
                .antMatchers("/app-api/**").permitAll()
                // 登录相关接口
                .antMatchers("/admin-api/system/auth/login").anonymous()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();

        SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    public OncePerRequestFilter createOncePerRequestFilter() {
        return new OncePerRequestFilter() {
            @Override
            protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
                // 获取token
                String token = request.getHeader("token");

                // 如果token不存在则放行
                if (StrUtil.isBlank(token)) {
                    filterChain.doFilter(request, response);
                    return;
                }

                // 解析token数据
                boolean verify = JWTUtil.verify(token, jwtSecret.getBytes(StandardCharsets.UTF_8));
                if (!verify) {
                    filterChain.doFilter(request, response);
                    return;
                }
                JWT jwt = JWTUtil.parseToken(token);
                String tenantId = (String) jwt.getPayload("tenantId");
                String loginUserId = (String) jwt.getPayload("loginUserId");
                String loginTime = (String) jwt.getPayload("loginTime");

                if (Objects.equals(loginUserId, "SHARE_TOKEN")) {
                    // 空threadLocalModel存入当前执行线程
                    UsernamePasswordAuthenticationToken authentication =
                            new UsernamePasswordAuthenticationToken(new LoginUserDetails(), null, Collections.emptyList());
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                    filterChain.doFilter(request, response);
                    return;
                }


                // 从redis获取loginUserDetails
                LoginUserDetails loginUser = loginUserDetailsRedisDAO.get(tenantId, loginUserId, loginTime);

                if (loginUser == null) {
                    filterChain.doFilter(request, response);
                    return;
                }

                // redis续期过期时间
                loginUserDetailsRedisDAO.refresh(tenantId, loginUserId, loginTime);

                Collection<? extends GrantedAuthority> authorities = StreamUtil.toList(loginUser.getScopes(), SimpleGrantedAuthority::new);


                // threadLocalModel存入当前执行线程
                UsernamePasswordAuthenticationToken authentication =
                        new UsernamePasswordAuthenticationToken(loginUser, null, authorities);
                SecurityContextHolder.getContext().setAuthentication(authentication);

                // 多租户上下文
                TenantContextHolder.setTenantId(tenantId);
                if (isIgnoreUrl(request)) {
                    TenantContextHolder.setIgnore(true);
                }

                filterChain.doFilter(request, response);
            }
        };
    }

    private boolean isIgnoreUrl(HttpServletRequest request) {
        // 快速匹配，保证性能
        if (CollUtil.contains(tenantConfig.getIgnoreUrls(), request.getRequestURI())) {
            return true;
        }
        // 逐个 Ant 路径匹配
        for (String url : tenantConfig.getIgnoreUrls()) {
            if (pathMatcher.match(url, request.getRequestURI())) {
                return true;
            }
        }
        return false;
    }
}
